home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Shareware Grab Bag
/
Shareware Grab Bag.iso
/
007
/
bintel10.arc
/
BINTEL.ASM
next >
Wrap
Assembly Source File
|
1987-11-01
|
53KB
|
1,400 lines
name BINTEL
page 78,132 ;set printer to compressed, 8 lines/inch [^O Esc '0' on Epson]
title BINTEL.COM utility to interconvert binary & Intel hex files
subttl -- Introduction, principles of program --
.radix 16 ;default number base for assembler = hexadecimal
Version equ '1.01'
Revised equ '10/10/87 16:10 '
; ============== REVISION HISTORY ==============
; 1.00 2/10/87 original version that works
; 1.01 3/16/87 fix bug in CommandArgs subroutine
;uploaded to BIX 11/1/87 in BINTEL10.ARC
comment `
MASM 4.00/5.00 PC/MS DOS 2.x Original: 2/10/87 by R. M. Baldwin.
COM format. Compiled on IBM PC-clone w/ 448K, V20 CPU, 4.8 MHz.
To assemble with Microsoft Macro Assembler:
masm bintel;
link bintel;
exe2bin bintel bintel.com
USE:
1. To convert a binary file BINARY.COM:
bintel binary ;creates BINARY.HEX in Intel format
2. To convert a hex file BINARY.HEX back to binary:
bintel binary /h ;creates BINARY.BIN
3. Entering bintel with nothing or bintel ? gives a brief explanation.
PURPOSE:
Converts a binary file to Intel hex format for transmission as Ascii file.
Main purpose is to download to Steve Ciarcia's Circuit Cellar Serial EPROM
Programmer (CCSP), which uses Intel Hex format. Applications are programming
EPROMS for a Z-80 based process controller.
BACKGROUND:
Intel format consists of a series of records called 'paragraphs', begun with
a colon (':') and ended with a CR. For example:
:100000003180083E89D303AFD300D301060711FF27 (1st paragraph -- 16 data bytes)
:0D001000FF21FFFF19DA140010F73E10D396 (2nd paragraph -- 13 data bytes)
:00000001FF (last paragraph; no data bytes)
The meaning of the fields in each paragraph:
Colon Data Address of Paragr Data (pairs of nibbles) CRC
Delim Count 1st data Type in ASCII form (CheckSum)
-iter byte (Attribute)
: 10 0000 00 31 80 08 3E ... FF 27
: 0D 0010 00 FF 21 FF FF ... D3 96
: 00 0000 01 {none} FF
All numbers are in upper-case Ascii hexadecimal representation.
Data count: # data bytes in the paragraph.
Address: address (offset) where 1st data byte goes.
Para type: 00 = not the end yet, 01 = last paragraph.
CRC: CheckSum = 2's complement of the 8-bit addition of the binary
values of all the other bytes. (ie the sum of the CRC plus all the
other bytes = 0.)
[Reference: Bill Curlew, Circuit Cellar BBS msg #4221 (10/27/86)]
OUTLINE OF PROGRAM:
1. Get input file and command switch(es) from command line.
2. Decide whether Binary --> Intel or vice versa.
3. Open input file.
Default file extension conventions:
binary file: BIN or COM (priority: BIN > COM)
hex file: HEX
4. Use buffers for input & output; allocate 3 X the space for the hex data.
5. Process binary file
a. Read a block from file into input buffer.
b. Read up to 16 bytes from buffer; if buffer exhausted, read block from
disk.
c. Store ':',then # bytes, then address, then '00', then the data bytes,
then the checksum, all in ASCII representation, then CR in output
buffer (+ LF for clarity).
d. If buffer fills up before end, append buffer contents to output file.
e. When end of file reached, append a null paragraph with '01' as the
paragraph attribute, ie ':00000001FF'.
5'. Process IntelHex file:
a. Read a block from file into input buffer.
b. Read bytes from buffer; if buffer empty, read block from disk. Skip
over chars until paragraph start (':') found. Then read next 2 bytes =
# data bytes.
c. Read in address offset, compare with calculated value, error if no
agreement. Ignore the file address anyway. Read paragraph attribute,
store. Get all data bytes, store in output buffer.
d. If buffer fills up before end, append buffer contents to output file.
e. Check CRC with that calculated. Error if they don't agree.
6. Write buffer to output file and close all files.
FUTURE ADDITION IDEAS:
1. Allow specifying start address.
2. Allow specifying groups of files with DOS wild cards in file spec;
eg, bintel proc*
3. Allow specifying a different drive\path and/or filename for output file.
4. Check for existing output files & confirm before overwriting.
EndComment `
subttl -- Declarations, equates, macros --
page ;start a new page
;--- Equates ---
;Ascii characters
Bel equ 7 ;^G console bell
Tab equ 9 ;^I
LF equ 0Ah ;line feed ^J
CR equ 0Dh ;carriage return ^M
Eof equ 1Ah ;end of file ^Z
Blank equ 20h ;space char
Space equ 20h ;space char
;DOS function calls
DOSver equ 30h ;get DOS version
Terminate equ 4Ch ;terminate with return code
TTYout equ 2 ;single char output to console (std out)
Pstring equ 9 ;print string ending with '$' to console (for DOS 1.x)
;File-related DOS calls
FileCreate equ 3Ch ;create or truncate file with AsciiZ
FileOpen equ 3Dh ;open file with AsciiZ
FileClose equ 3Eh
ReadFile equ 3Fh ;R/W file or device with AsciiZ spec
WriteFile equ 40h
;Device handles
StdOut equ 1 ;standard output (CON) redirectable
StdErr equ 2 ;standard error device (CON) (not redirected)
ConOut equ 2 ;non-redirectible output (CON)
;--- program-specific equates ---
CommandTail equ 80h ;address of command tail in PSP (Program Segment Prefix)
SwitchChar equ '/' ;for adding commands from DOS
HexCode equ 'H' ;command for processing hex file to binary
Dot equ '.' ;for finding file extension
ParStart equ ':' ;delimiter for beginning of IntelHex 'paragraph'
ParEnd equ CR ; " " end " " "
DOSpath equ 40h ;max # chars in DOS path/file spec
RecLen equ 10h ;size of 'paragraph': default 16 bytes
HexOverHead equ 13d ;# extra bytes overhead for a hex paragraph; ie:
; ':nnaaaatt...cc'<CR><LF>
; (# Hex bytes) = (# binary bytes) * 2 + HexOverHead
BinBufSize equ 10h*RecLen ;binary buffer size in 'paragraphs'
HexBufSize equ 3*BinBufSize ;3* for hex (16 bytes binary -->45 in hex format)
;--- Macros ---
DOScall Macro Function ;; invoke int 21h with AH=function
mov AH, &Function
int 21h
endM
Write Macro Dev, Msg, Len ;;write Msg (Len bytes long) to Dev
mov DX, offset &Msg ;;DS:DX points to address of buffer
mov CX, &Len ;;CX = # bytes to write
mov BX, &Dev ;;BX = device or file handle
DOScall writeFile ;;write to file or device
endM
Read Macro Dev, Buf, Len ;;read Len bytes from Dev into Buf buffer
mov DX, offset &Buf
mov CX, &Len
mov BX, &Dev
DOScall readFile
endM
EchoZ macro Str ;;echo string Str terminated with binary 0
push SI ;;save SI
mov SI, offset &Str ;;subroutine will use string at DS:SI
call EchoString
pop SI
endM
subttl -- Main program -- Variable data area --
page
;================
Code segment para public 'CODE'
assume CS:Code,DS:Code,ES:Code,SS:Code
org 100h ;start at offset 100h for COM file
Main proc far ;main procedure
jmp Main0 ;skip variable area
;--- strings ---
Header db CR
db 'BINTEL.COM Binary-Intel Hex Converter, Version ', Version,' (1987) R. M. Baldwin'
db CR, LF
HeaderLen equ $-Header
db 'Last revision: ',Revised ;makes it easy to keep track of .COM file.
;--- variables --- (buffers and messages stored at end)
BinExt1 db '.BIN' ;Default extension for binary file
BinExt2 db '.COM' ;alternate " " " "
HexExt1 db '.HEX' ;default extension for Ascii file
HexExt2 db '.HEX' ;alternate " " " "
ProcCode db 0 ;0 means process binary file, non-zero means hex
ErrorCode db 0 ;Return code with DOS function 4Ch.
; Can check in DOS batch files:
; eg, 'IF NOT ERRORLEVEL 1 GOTO NOERROR'
;0 = no error
;1 = serious error, 2=warning, eg overflow in address counter.
InBufTail dw 0 ;pointer to end of current data in input buffer
LastPar db ParStart,'00000001FF', ParEnd ;Last paragraph, always
db LF, Eof ; appended as last record.
LastParLen equ $-LastPar ; Add Eof mark (^Z) for luck.
InFile dw 0 ;handle returned by DOS -- initialize to 0
InFileName db DOSpath dup(0) ;Asciiz string for input file
OutFile dw 0 ;handle returned by DOS; pre-set to 0 as
; signal that file not opened yet
OutFileName db DOSpath dup(0) ;AsciiZ for output file
LineCount dw 1 ;Line # in hex file (start with #1; will always have atleast 1)
ParaCount dw 1 ;paragraph #
Address dw 0 ;address offset for first byte in paragraph ;NOTE: offset is
; from start of file; ie, COM files lose 100h offset.
NumBytes dw 0 ;# bytes in current paragraph. Use word to facilitate loading CX
LastParagr db 0 ;code to indicate last paragraph (0=no, 1=yes)
subttl -- Main program --
page
;-----------
Main0: ;start of main program
DOScall DOSver ;check DOS version
cmp AL,2 ;DOS 2 or greater?
jae Main1 ;OK
;DOS<2.0:
mov DX,offset DOSerr ;use DOS 1.x print string function
DOScall Pstring ; to display error message
int 20h ; & exit to operating system
Main1:
write StdOut,Header, HeaderLen ;display header
call CommandArgs ;get command from PSP
;this routine changes SI, DI, AL.
;it sets carry flag to signify that either no file spec was entered on
; command line, or a '?' was entered, thus triggering explanation.
;IF SUCCESSFUL (Cy = 0),
; InFileName = ParamStr(1),
; ProcCode = 1 if hex switch entered, 0 otherwise.
jnc Main2 ;Cy set triggers explanatory message.
write StdOut, HelpMsg, HelpLen ;display explanatory message
jmp ErrExit ; & exit with 'error' code
Main2: ;InFile now contains input file spec
mov AL,ProcCode ;convert binary-->Intel or vice versa?
or AL,AL ;zero is default
jz ProcBin ;process binary file if 0 (default)
jmp ProcHex ;if not zero, process hex-->binary (note jmp > 127 bytes)
subttl --- Process Binary File ---
;++++++++++++++++
ProcBin proc near ;process a binary file
mov BP, offset BinExt1 ;point to address of 1st def ext for Open subroutine
call OpenInput ;try to open input binary file
;on entry, BP = address of 1st default ext.; the 2nd is stored right after
;on exit, Cy flag will be set if error, clear if Ok
;AX, CX, & SI are altered
jnc ProcBin1 ;Cy indicates file open error
jmp ErrExit ;this is a long jump (> 127 bytes)
ProcBin1:
;FUTURE: add check for file already exists, overwrite (Y/N)?
mov BP, offset HexExt1 ;point to def ext for output file
call OpenOutFile ;open output file & truncate
jnc ProcBin1a ;Cy set on error
jmp ErrExit
ProcBin1a:
lea DI, HexBuffer ;initialize pointer to output buffer
write StdOut, ProcBinMsg, ProcBinLen ;report progress to console
echoZ InFileName
write StdOut, BinHead, BinHeadLen ;header for paragraph address
;at this point, both InFile & OutFile are open & ready to go
;DS:SI = input buffer pointer; ES:DI = output buffer pointer
;InBufTail points to end of data in input buffer
;HexBuffer + HexBufSize is the end of the output buffer
ReadBlock: ;read a block of data from input [binary] file
;BX= file handle (InFile)
;CX= # bytes requested = input buffer size (BinBufSize)
;DX= input buffer area (BinBuffer)
;IF SUCCESSFUL,
; AX=# bytes actually read, CX=# bytes requested (BinBufSize)
; if AX=0 then end of file reached.
; Data stored starting at BinBuffer.
;IF UNSUCCESSFUL, AX= error code, Carry flag set.
mov BX, InFile ;input file
mov CX, BinBufSize ;# bytes to [try to] read from file
mov DX, offset BinBuffer ;point to input buffer
DOScall readFile ;function 3Fh
jnc ProcBin2 ;NOTE: Carry flag set if error occured
call ReadError ;error, display msg
jmp FinishOff ; & exit
ProcBin2: ;read Ok, no error
cmp AX,0 ;end of file?
jnz ProcBin3 ;no, continue; [short jump]
jmp LastParag ;EOF, write last paragraph & finish [long jump > 127 bytes]
ProcBin3: ;continue processing file
lea SI, BinBuffer ;start at beginning of input buffer
;address of end of data area = InBuffer + # bytes read in - 1
;AX contains # bytes read in; SI points to next data byte to be read.
;InBufTail = AX + SI - 1
add AX, SI ;AX = # bytes + data pointer; points to address after last byte
dec AX ;AX = address of last data byte
mov word ptr InBufTail, AX ;store it
;---------------- main processing routine -------------------
ProcParag: ;process next paragraph
;1st check to see if input buffer empty yet
mov CX, InBufTail ;Address of last byte of data.
cmp SI,CX ;Are we beyond the end?
ja ReadBlock ;Yes, input buffer exhausted, read another block from disk
;calculate # bytes left in buffer = CX - (SI-1) = (CX-SI) + 1
sub CX, SI ;CX= (CX-SI)
inc CX ;CX= (CX-SI) + 1
;determine # data bytes in this paragraph (CX contains # bytes)
mov AX,RecLen ;# bytes in a paragraph
cmp CX, AX ;(# bytes Left) <= (1 paragraph) ?
jbe ProcPar1 ;if less or equal, use value in CX for # data bytes;
mov CX, AX ; else, use # bytes in a paragraph
;CX contains # data bytes in this paragraph
ProcPar1: ;see if there's room in output buffer for this paragraph
;NOTE: DI points to next open position in output buffer
;NOTE: # bytes required in output buffer = HexOverHead + 2*(# data bytes)
mov BX,CX ;BX= (# data bytes)
shl BX,1 ;BX= 2* (# data bytes)
add BX,HexOverHead ;BX= HexOverHead + 2 * (# data bytes) = # bytes req'd in output buffer
;NOTE:
; End of output buffer = HexBuffer + HexBufSize
; Last byte address +1 = DI + BX
mov AX, offset HexBuffer
add AX, HexBufSize ;AX = address of end of output buffer
add BX, DI ;BX = address of last byte + 1
cmp BX, AX ;is there room?
jbe ProcPar2 ;yes, there's room, continue
;not enough room, write output buffer to disk.
mov BP, offset HexBuffer ;point to output buffer
call WriteOutput ;NOTE: WriteOut proc preserves registers AX-DX,
; resets DI to point to start of OutBuffer
; sets carry flag to indicate write error
jnc ProcPar2 ; no error, continue
jmp FinishOff ;write error occured: -- abort.
ProcPar2:
xor BX, BX ;BL=0; initialize check sum (CRC); will be kept in BL
;NOTE: DI points to next position to store data in output buffer;
; STOSx instructions automatically increment DI according to byte or word.
; ConvHex subroutine converts byte in AL into the Ascii representation of the
; byte in AX; AH= char of most significant nibble, AL= least significant.
;store paragraph beginning delimiter
mov AL, ParStart
stosb
;store # data bytes in this paragraph
mov AX, CX ;AL now contains # bytes (since paragraphs always < 255)
add BL, AL ;add to checksum
call ConvHex ;convert byte in AL into Ascii representation in AX
stosw ; & store it in output buffer
;store file address offset of this paragraph
mov AX, Address ;get address value (16 bits)
;MSB
mov AL, AH ;store high byte first
add BL, AL ;add to checksum
call ConvHex ;convert byte in AL into Ascii representation in AX
stosw ; & store it
mov AX, Address ;get address value again
;LSB
add BL, AL ;AL= low byte; add to checksum
call ConvHex ;convert
stosw ; & store it
call EchoAddr ;echo paragraph address at (DI-4)
;routine preserves all regs
;update address value --
mov AX, Address
add AX, CX ; add # bytes in current paragraph
mov word ptr Address, AX ; store updated value of address
jnc ProcPar3 ;check for overflow on large file from ADD instr.
;if input binary file has more than 64K bytes, address field will overflow
;display error message as warning, but proceed with process
push AX ;save registers used in writing msg
push BX
push CX
push DX
write StdOut, OverflowMsg, OverflowLen
mov ErrorCode, 2 ;store code for 'non-fatal' error, address overflow
pop DX ;restore registers
pop CX
pop BX
pop AX
ProcPar3: ;continue with processing:
;store paragraph attribute; will always be '00' because last paragraph done elsewhere
mov AX, 3030h ;'00' in ASCII
stosw ;NOTE: doesn't contribute to check sum
;store data bytes -- NOTE: SI points to data byte in input buffer
NextByte: ; CX = # data bytes to process
lodsb ;load data byte into AL
add BL, AL ;update check sum
call ConvHex ;convert byte to Ascii representation in AX
stosw ; & store it in output buffer
loop NextByte ;get next byte
;now take 2's complement of check sum
neg BL ;CRC = - (check sum)
mov AL, BL ;convert to ascii representation
call ConvHex
stosw ; & store it
mov AL, ParEnd ;store the paragraph ending delimiter (carriage return)
stosb ; in output buffer
;could make this optional via switch /L eg.
mov AL,LF ;Add a line feed for clarity in displaying file
stosb
jmp ProcParag ;go back & process next paragraph
;----------------
LastParag: ;no more data, write last paragraph
;NOTE: last paragraph always same format, a null paragraph
; with format LastPar = ':00000001FF {CR}{LF}'
; LastParLen = HexOverHead bytes
;check if room for last paragraph
;NOTE: DI points to next open position in output buffer
mov BX, LastParLen ; # bytes to write
;NOTE: End of output buffer = OutBuffer + HexBufSize
; Last byte address +1 = DI + BX
mov AX, offset HexBuffer
add AX, HexBufSize ;AX = address of end of output buffer
add BX, DI ;BX = address of last byte + 1
cmp BX, AX ;is there room?
jbe LastPar1 ;yes, there's room, continue
;not enough room, write output buffer to disk.
mov BP, offset HexBuffer ;WriteOut expects address of buffer in BP
call WriteOutput ;NOTE: WriteOut proc preserves registers AX-DX,
; resets DI to point to start of OutBuffer, sets Cy if error.
jnc LastPar1 ;Cy=0 no error
jmp FinishOff ;carry set: write error occured: -- abort.
LastPar1: ;copy last paragraph string into output buffer
mov CX, LastParLen ;# bytes to copy
mov SI, offset LastPar ;point to string to copy
;NOTE: DI still points to destination in OutBuffer
rep movsb ;copy the string
;& write the output buffer to disk
mov BP, offset HexBuffer ;output buffer address
call WriteOutput ;write to disk (registers are preserved)
;if write error occured, ErrorCode=1
jmp FinishOff
ProcBin endP ;end of process binary procedure
subttl --- Process Intel Hex File ---
;++++++++++++++++
ProcHex proc near ;process an input file in Intel Hex format
mov BP, offset HexExt1 ;point to address of 1st def ext for Open subroutine
call OpenInput ;open input file -- (AX, CX, & SI are altered)
jnc ProcHex1 ;Cy indicates file open error
jmp ErrExit ;this is a long jump (> 127 bytes)
ProcHex1:
; FUTURE: add check for file already exists, overwrite (Y/N)?
mov BP, offset BinExt1 ;default extension for output file passed in BP
call OpenOutFile ;open output file & truncate (AX-DX, SI, DI are altered).
jnc ProcHex2 ;Cy set on error
jmp ErrExit ;long jump
ProcHex2:
write StdOut, ProcHexMsg,ProcHexLen ;report on progress
echoZ InFileName
write StdOut, HexHead, HexHeadLen ;header for line # & paragraph #
;initialize variables
lea DI, BinBuffer ;initialize pointer to output buffer
;at this point, input file = hexfile; output file = binary file, both open.
;DS:SI --> input buffer (HexBuffer), ES:DI --> output buffer (BinBuffer)
; variable Address pre-initialized to 0000
ProcHex3: ;force disk read on first pass
mov SI, offset HexBuffer
mov word ptr InBufTail, SI ;end of input buffer = start of buffer
inc SI ; SI = end + 1
;---------------- main hex processing routine --------------
HexPar: ;process next paragraph
;look for paragraph start char (':')
call GetChar ;routine gets next char from input buffer & puts in AL,
; loads from disk if needed.
jnc HexPar0
jmp HexFinish ;Carry flag set indicates End of file
HexPar0:
cmp AL, ParStart
jz HexPar2 ;paragraph start found, get next char
cmp AL, CR ;end of line?
jnz HexPar1 ;no, check for next
;Eol, increment line counter
mov AX, LineCount ;first echo current line #
call writeCR ;return csr to beginning of line (regs preserved)
call WriteNum ;display value in AX as decimal number on screen
inc AX ;increment line counter.
mov word ptr LineCount, AX
jmp short HexPar ;keep looking for paragraph mark
HexPar1:
cmp AL, Eof ;end of file?
jnz HexPar ;no, keep looking
jmp HexFinish ;End of file, finish
HexPar2: ;paragraph marker found, now process hex paragraph.
;GetHexByte routine reads 2 bytes as ASCII chars, converts to byte value in AL
;Non-hex digits (0-9, A-F, a-f) are ignored.
;Keep check sum (CRC) in BL.
call GetHexByte ;get # data bytes: AL = # bytes in this paragraph
jc HexParExit ;Cy set if Eof encountered
mov BL, AL ;1st value into CRC
cbw ;convert byte value in AL into word value in AX
mov word ptr NumBytes, AX ;save # bytes
;get address from file: 16-bit word
call GetHexByte ;high byte of address in AL
jc HexParExit ;Cy set if Eof encountered
add BL, AL ;add to CRC
mov CH, AL ;high byte into hi-order part of CX
call GetHexByte ;AL = low order byte
jc HexParExit ;Cy set if Eof encountered
add BL, AL ;update CRC
mov CL, AL ;low byte into lo-order part of CX; CX = address from file
;check to see if any data bytes indicated in this paragraph
mov AX, NumBytes
or AX, AX ;# bytes = 0?
jz HexPar4 ;skip address check if no data bytes indicated
;else, check calc'd address against hex file's value
mov AX, Address ;address as calculated from # bytes read
cmp AX, CX ;do they agree?
je HexPar3 ;Ok
;no, don't agree, display warning but continue with calc'd value
push AX ;save needed regs used in write
push BX
push DX
push CX ;CX = address derived from file
;explicitly echo line # & paragraph #
call writeCR ;return cursor to beginning of line
mov AX, LineCount ;echo line # & paragraph #
call WriteNum
mov AX, ParaCount
call WriteNum
write StdOut, AddrErrMsg, AddrErrLen
mov ErrorCode, 1 ;return code for 'serious' error (is it?)
pop CX ;retrieve file's address
mov AX, CX ; & display it
call WriteNum ; on screen (future version: show hexadecimal address)
call NewLine ;CRLF preserving regs
pop DX ;retrieve rest of regs
pop BX
pop AX
HexPar3: ;increment address counter by # bytes in this paragraph
mov CX, NumBytes
add AX, CX
mov word ptr Address, AX
HexPar4: ;get paragraph attribute
call GetHexByte
jc HexParExit ;Cy set if Eof encountered
add BL, AL ;increment CRC
cmp AL, 0 ;non-zero means last paragraph
jz HexPar5
mov byte ptr LastParagr, AL ;store code as signal
HexPar5:
mov CX, NumBytes ;load loop counter with # data bytes
cmp CX, 0 ;no data bytes?
jz HexPar8 ;skip if no data bytes
jmp short HexPar6 ;skip past HexParExit routine
HexParExit: ;stick this in here so relative jumps are within range
write StdOut, PrematureMsg, PrematureLen
mov ErrorCode, 1 ;code for serious error. Probably not Hex format.
jmp short HexFinish
HexPar6: ;store data bytes in output buffer
;DI= curr pos in out buffer, end = BinBuffer + BinBufSize
mov DX, offset BinBuffer
add DX, BinBufSize ;DX = BinBuffer+BinBufSize = addr of end of buffer+1
dec DX ;DX = addr of last position in output buffer
cmp DI, DX ;are we beyond?
jbe HexPar7 ;no, Ok, skip
;no room, flush buffer to disk
mov BP, offset BinBuffer ;point to output buffer
call WriteOutput ; & write to disk (regs are preserved, DI = BP)
jc HexFinish ;carry flag set means write error; abort
HexPar7:
call GetHexByte ;get next value from input buffer (AL=byte, BX-DX preserved)
jc HexParExit ;Cy set if Eof encountered
stosb ;store value in output buffer.
add BL, AL ;add value to CRC
loop HexPar6
HexPar8: ;check CRC
neg BL ;take 2's complement
call GetHexByte ;AL = file's CRC
jc HexParExit ;Cy set if Eof encountered
cmp AL, BL ;compare with calc'd CRC
je HexPar9 ;OK, they agree.
;CRC's don't agree, show err msg but continue
push BX
push CX
push DX
call writeCR ;return cursor to beginning of line
mov AX, LineCount ;echo line # & paragraph #
call WriteNum
mov AX, ParaCount
call WriteNum
write StdOut, CRCerrMsg, CRCerrLen
mov ErrorCode, 1 ;code for serious error -- this is the whole point
;of Intel Hex format, after all.
pop DX
pop CX
pop BX
HexPar9:
mov AX, ParaCount ;echo paragraph #
call WriteNum
inc AX ;increment paragraph counter
mov word ptr ParaCount, AX
jmp HexPar ;look for next paragraph [long jump]
HexFinish: ;finish hex file, flush buffer to disk
mov BP, offset BinBuffer
call WriteOutput
jmp short FinishOff
ProcHex endP
subttl -- Main procedure --
;++++++++++++++++
FinishOff: ;end of input file reached. Close it up
mov BX,InFile ;close input file
call CloseFile
jnc FinishOff1
;routine will set Cy if error
mov byte ptr ErrorCode,1 ;set flag for serious error
write StdOut,CloseErrMsg,CloseErrLen
echoZ InFileName
FinishOff1: ;close output file
mov BX, OutFile ;file handle
cmp BX,0 ;Output file opened yet?
jz Exit ;if not, just exit
call CloseFile ; else, close file first
jnc Exit
write StdOut, CloseErrMsg, CloseErrLen
echoZ OutFileName
jmp short ErrExit ;exit with error code
FatalError: ;something inexplicable occurred
write StdErr, FatalErrMsg, FatalErrLen
ErrExit: ;'generic' error exit
mov AL,1 ;exit with return code 1 (fatal error) for DOS
DOScall Terminate ; ERRORLEVEL check
Exit:
mov AL,ErrorCode ;get error code from memory
;if error, return code will be set accordingly
;else, exit to DOS with 0 return code (AL=0)
DOScall Terminate ; for 'no error'.
Main endP
subttl -- Subroutines --
;--- End of Main Program ---
page
;+++ Subroutines +++
Upcase proc near ;convert char in AL to upper case
cmp AL,'a' ;is it lower case letter?
jb UpcaseExit ;no
cmp AL,'z'
ja UpcaseExit
sub AL,'a'-'A' ;convert to upper case
UpcaseExit:
ret
UpCase endP
;++++++++++++++++++++
CommandArgs proc near ; get command line arguments
;destroys SI, DI, AL
; returns carry flag clear if successful, Cy=1 if not
mov byte ptr ProcCode, 0 ;pre-set code to process binary --> hex by default
mov SI, CommandTail ;DS:SI points to command line address
mov DI, offset InFileName ;ES:DI points to filename location
cld ;direction flag upward
lodsb ;get 1st byte -- contains # chars in cmd tail
or AL,AL ;anything there?
jz CAerr ;if not, exit with error stat
CommandArgs1: ;else, read it. Skip over blanks to 1st char
lodsb ;read byte at [SI] into AL
cmp AL,CR ;CR or switch command with no file spec is 'error'
je CAerr ; exit with explanatory msg
cmp AL, SwitchChar
je CAerr
cmp AL,Blank ;is it a blank?
jbe CommandArgs1 ;skip blanks [and control chars]
;next char
CommandArgs2: ;found char
cmp AL,'?' ;question mark?
je CAerr ;can't be file spec, exit with error to force explanation
call Upcase ;convert char in AL to upper case
stosb ;store it in filename buffer
lodsb ;get next char
cmp AL, CR ;carriage return yet?
je CAexit ;yes, end of command tail
cmp AL, SwitchChar ;command switch?
je CommandArgs4 ;yes, end of file spec
cmp AL, Blank ;blank?
jne CommandArgs2 ;no, go back for more chars
;process switch commands
CommandArgs3:
;char was blank, check for more commands
lodsb ;get next char in cmd tail
cmp AL,CR ;CR?
je CAexit ;yes, no more commands.
cmp AL,Blank ;blank?
je CommandArgs3 ;skip over blanks and non-switch chars
cmp AL,SwitchChar ;switch command ('/')?
jne CommandArgs3 ;until switch found
CommandArgs4: ;switch was found
lodsb ;get next char
call Upcase ;convert to upper case
cmp AL,HexCode ;is it command to process a hex file?
jne CommandArgs5 ;no, check other switches
mov byte ptr ProcCode, HexCode ;non-zero indicates process hex file
jmp short CommandArgs3 ;Look for next command
CommandArgs5: ;put other switch code checks here
;char after switch doesn't match a command: not really an error, just show msg & ignore
mov byte ptr SwitchCode, AL ;put char in position in BadSwitchMsg
write StdOut, BadSwitchMsg, BadSwitchLen ; & display it
jmp short CommandArgs3 ;Look for next command
;ignore rest of command tail
CAexit: ;normal exit
clc ;clear carry flag
ret
CAerr: ;'error' occured (nothing on cmd line)
stc ;set carry flag
ret
CommandArgs endP
subttl -- Subroutines -- file-related --
;+++++++++++++++++++++
OpenInput proc near ;add default extensions & open input file
;Input file is AsciiZ string at InFileName
;if no extension is entered, the routine tries to open input file with
; one of two default extensions.
;on entry, BP = address of 1st default extension; the 2nd is stored right after it.
;on exit, Cy flag will be set if error, clear if Ok
;AX, CX, & SI are altered
;check if extension was entered
cld
lea SI, InFileName ;point to start of string
OpenInput1:
lodsb ;get char
cmp AL,0 ;end of string?
jz OpenInput2
cmp AL,Dot ;is it '.'?
jnz OpenInput1 ;no, get next char
;file spec includes a dot, assume an extension was intended
jmp OpenInput3 ;skip to open file
OpenInput2: ;no dot in file spec, add default extension
;NOTE that DI points to end of AsciiZ string from previous op
mov SI, BP ;address of 1st default ext passed to subroutine in BP
mov CX,4 ;# bytes in '.xxx'
rep movsb ;put extension in place
;try to open the file
call OpenInFile
jnc OpenInputexit ;open successful
;couldn't open 1st default, try alternate ext
sub DI,4 ;back up to extension position again
;NOTE: SI is pointing to 2nd default ext, stored immediately after 1st
mov CX,4 ;# bytes in '.xxx'
rep movsb ;put extension in place
;and proceed
OpenInput3:
call OpenInFile ;try to open the file for reading
jnc OpenInputexit ;open successful, return
;error occurred, display message
;for future version, could use code in AX for more specific msgs
write StdOut, OpenErrMsg, OpenErrLen ;generic error msg for simplicity
echoZ InFileName ;echo filename
OpenInputErrExit:
stc ;carry indicates error in open
ret
OpenInputexit: ;normal exit,
clc ;clear carry flag
ret
OpenInput endP
;+++++++++++++++++++++
OpenInFile proc near ;open file for input
mov DX,offset InFileName ;DS:DX points to asciiz filename
sub AL, AL ;AL=0 access is read only
DOScall FileOpen
jnc OifExit ;if error, AX = error code, Cy set
ret
OifExit:
mov word ptr InFile,AX ;if successful, AX=file handle
ret
OpenInFile endP
;+++++++++++++++++++++
OpenOutFile proc near ;create file for output
;on entry, BP = address of extension for output file
; regs AX-DX, SI, DI are altered
;copy InFileName
; look for '.' in extension
cld ;direction upward
lea DI, InFileName ;point to start of string
mov CX,DOSpath ;max # chars in file name string
mov AL,Dot ;char to scan for
repne scasb ;scan for byte in AL
neg CX ;find position; pos('.')=DOSpath - CX
add CX,DOSpath ;CX= -CX + DOSpath
jz OofErr ;end reached, no dot = error at this point
;else, CX now contains offset of dot
dec CX ;leave off the dot
lea SI, InFileName
lea DI, OutFileName
repe movsb ;copy InFileName to OutFileName
mov SI, BP ;BP= address of default extension for output file
;DI still points to end of filename
mov CX,4 ;length of '.xxx'
repe movsb ;copy ext
;OutFileName now is AsciiZ str with same filename as InFile, but output default ext.
;create file for output
mov DX, offset OutFileName ;DS:DX points to Asciiz
mov CX,0 ;normal file attribute
DOScall FileCreate ;create or truncate file function
jnc OofExit
OofErr: ;error, display generic message
write StdOut, OpenErrMsg, OpenErrLen
echoZ OutFileName
ret ;Cy set to indicate unsuccessful open
OofExit:
mov word ptr OutFile, AX ;else AX = file handle
ret ;store it & return
OpenOutFile endP
;+++++++++++++++++++++
CloseFile proc near ;close file whose file handle is in BX
cmp BX,0 ;avoid inadvertently "close"ing keyboard
jnz CF1
stc ;set carry to indicate error
ret
CF1:
DOScall FileClose ;if successful, Cy=0
ret ;if error, AX = error code, Cy set
CloseFile endP
;+++++++++++++++++++++
ReadError proc near ;error occurred in reading file
; Alters regs AX-DX
write StdErr, ReadErrMsg, ReadErrLen ;display generic read error msg
echoZ InFileName ; echo filename
mov ErrorCode, 1 ;set error flag for FinishOff procedure
ret
ReadError endP
;+++++++++++++++++++++
WriteOutput proc near ;write the contents of the output buffer to disk
;on entry, BP = address of output buffer
;NOTE: DI points to last byte in output buffer
;BX = file handle = OutFile
;CX = # bytes to write = DI - OutBuffer
;DX = buffer area = OutBuffer
;on return, IF SUCCESSFUL, AX= # bytes written; set Cy if AX < CX
; IF UNSUCCESSFUL, AX= error code (disregard), Cy set
push AX ;save all registers used
push BX
push CX
push DX
mov DX, BP ;starting address of buffer
mov CX, DI ;calculate # bytes
sub CX, DX ; = DI - OutBuffer
mov BX, OutFile
DOScall writeFile ;function 40h
jc WriteOut1 ;Cy set indicates error
cmp AX, CX ;# bytes written same as req'd ?
jz WriteOut1 ;Ok
;# bytes not same may mean disk full, just display 'generic' error to keep it simple
write StdErr, WriteErrMsg, WriteErrLen
echoZ OutFileName
mov byte ptr ErrorCode, 1 ;Return code for serious error
stc ;Cy set signals error to calling routine
WriteOut1:
pop DX ;restore registers
pop CX
pop BX
pop AX
mov DI, BP ;reset output buffer pointer
ret
WriteOutput endP
subttl -- Subroutines for character processing --
;+++++++++++++++++++++
ConvHex proc near ;convert the byte in AL into ascii representation in
; AX, with Most Significant digit in AH,
; Least Significant in AL
push dx
mov AH,AL ;copy the value
;most significant
and AH,0Fh ;mask off high nibble
mov DL,AH ;use D register for calculation
call HexDigit ;convert DL into ascii hex digit
mov AH,DL ;MSB now in AH
;least significant
and AL,0F0h ;mask off low nibble
shr AL,1 ;shift hi into lo
shr AL,1
shr AL,1
shr AL,1
mov DL,AL
call HexDigit
mov AL,DL ;LSB in AL
pop dx
ret
ConvHex endP
;+++++++++++++++++++++
HexDigit proc near ;convert byte in DL into ascii hex digit
add DL,'0' ;add Ascii offset
cmp DL,'9' ;'A' - 'F' ?
jbe HexDigit1 ;no, that's it.
add DL,'A'-1-'9' ; else, correct for ascii offset to get A-F
HexDigit1:
ret
HexDigit endP
;+++++++++++++++++++++
GetHexByte proc near ;load 2 consecutive bytes from input buffer,
; & convert to byte value in AL.
;routine converts to upper case; rejects all chars other than 0-9, A-F, a-f
;ON ENTRY, DS:SI points to next char to read
;ON EXIT, AL = byte value; SI = position of next char, AH altered (=0)
; if Eof, AX = 0, carry flag set
push BX ;save regs
push CX
push DX
;high byte
call GetDigit ;read next char & convert to binary value in AL
jc GetHexByteExit ;carry set to indicate end of file
mov AH, AL ;save value
;low byte
call GetDigit ;read next char & convert to binary value in AL
jc GetHexByteExit ;carry set to indicate error
shl AH, 1 ;arithmetic to reassemble byte value = 2^4 * AH + AL
shl AH, 1
shl AH, 1
shl AH, 1 ;AH = 16 * AH
add AL, AH ;AL = AL + 16*AH
cbw ;convert byte in AL into word in AX
GetHexByteExit:
pop DX ;restore regs
pop CX
pop BX
ret
GetHexByte endP
;+++++++++++++++++++++
GetDigit proc near ;get a hex digit from input buffer; ignore all chars other
; than '0'..'9','A'..'F' (upper or lower case)
;ON ENTRY, SI=next byte to read
;ON RETURN, if Eof, AX = 0, Carry Flag set
; if not Eof, AL = valid hex digit (upper case), Cy clear
call GetChar ;get char into AL
jc GetDigitExit ;carry flag set by GetChar to indicate end of file
call UpCase ;convert char in AL to upper case
;check for valid char in ['0'..'9', 'A'..'F']
cmp AL, '0'
jb GetDigit ;< '0' no good, get next char
cmp AL, 'F'
ja GetDigit ;> 'F' n.g.
cmp AL, '9'
jbe GetDigit1 ;numeral between 0 and 9
cmp AL, 'A'
jb GetDigit ;invalid char between '9' & 'A' = '['..'`'
sub AL, 7 ;valid hex digit A-F, adjust for diff between 'A' & '9'
GetDigit1: ;valid hexadecimal digit (adjusted if necessary)
sub AL, '0' ;convert to binary value
clc ;clear carry flag for exit
GetDigitExit: ;if EOF, Carry flag set on exit
ret
GetDigit endP
;+++++++++++++++++++++
GetChar proc near ;get next char from input buffer, reading more from disk
; if end of input buffer reached.
;ON ENTRY, SI = address of next position in input buffer
;ON EXIT, AL = next char, SI incremented
; if Eof, AX = 0, carry flag set, SI unchanged
; regs AX-DX, SI changed
;have we reached the end of the input buffer yet?
mov CX, InBufTail ;addr of last byte of data
cmp SI, CX ;beyond end yet?
jbe GetChar1 ;Ok, get next char from buffer
;else read another block from disk
;+++++++++++++++++++++
ReadHexBlock proc near ;get a block of data from input file
;uses regs AX-DX, SI
;if Eof, AX = 0
;else SI= start of buffer, InBufTail= end of buffer area (= AX)
; CX= # bytes requested
push AX ;save regs changed in proc
push BX
push CX
push DX
mov BX, InFile ;input file handle
mov CX, HexBufSize ; " " buffer size = # bytes to read
lea DX, HexBuffer ;point to input buffer to store data
;NOTE: file pointer automatically adjusted by DOS
DOScall ReadFile
jnc ReadHexBlock3 ;Cy flag set if error
call ReadError ;error, display msg
jmp FatalError ; & exit without further ado.
ReadHexBlock3: ;read Ok, no error
cmp AX, 0 ;end of file? (AX = # bytes actually read)
jnz ReadHexBlock4 ;if End of file, AX= 0
stc ;Eof, exit with carry flag set to signal calling routine
jmp short ReadHexBlockExit
ReadHexBlock4: ;reset variables for start of data block
;calculate end of data in input buffer =start addr + # bytes - 1
lea SI, HexBuffer ;point to start of input buffer
add AX, SI ;# bytes + address of buffer
dec AX ;AX = # bytes + StartAddress - 1 = end of input buffer
mov word ptr InBufTail, AX ;store address of last data char
ReadHexBlockExit:
pop DX ;restore regs
pop CX
pop BX
pop AX
ReadHexBlock endP ;note use of proc/endP here only for neatness; not a subroutine
;+++++++++++++++++++++
jc GetCharExit ;Eof signaled by Cy flag
GetChar1:
lodsb ;load next char from input buffer into AL
clc ;clear carry for not EoF
GetCharExit:
ret
GetChar endP
;+++++++++++++++++++++
WriteNum proc near ;display the 16-bit # in AX on screen as decimal #
;AX-DX are used, but preserved
push AX
push BX
push CX
push DX
push AX ;save value to be displayed since DOScall uses AH
mov DL, Blank ;precede # with a blank
DOScall TTYout
pop AX ;AX = value to be displayed
mov CX, 10d ;the denominator (change this value for other bases)
call Bin2Dec ;convert to decimal #. ON RETURN,
; CH = 10000's CL destroyed
; BH = 1000's BL = 100's
; DH = 10's DL = 1's
push DX ;save decade counter values during DOS function call
cmp CH, 0 ; is # > 9999?
jnz WriteNum1 ;avoid printing a '0' for numbers < 10,000
mov CH, Blank-'0' ;put a value in CH that will result in writing a blank
; when EchoDig adds '0' value
WriteNum1:
mov DL, CH ;write 10000's digit
call EchoDig
mov DL, BH ;1000's
call EchoDig
mov DL, BL ; 100's
call EchoDig
pop CX ;retrieve 10's & 1's values (pushed on the stack as DX) in CX
mov DL, CH ; 10's
call EchoDig
mov DL, CL ; 1's
call EchoDig
pop DX
pop CX
pop BX
pop AX ;note: routine returns with original value unchanged
ret
WriteNum endP
;+++++++++++++++
EchoDig proc near ;convert binary value of decimal digit to ASCII digit
;DL = binary value (0-9) of digit to display
add DL, '0' ;convert to ASCII
DOScall TTYout ;send to console
ret
EchoDig endP
;+++++++++++++++
Bin2Dec proc near ;Convert 16-bit word in AX into decimal equivalent for display.
;Use 'bins'technique: progressively subtract ten from number,
; storing number in bins corresponding to 1,10,100,1000,10000.
;ON ENTRY, AX = 16-bit integer, CX (CL) = divisor (10d)
;ON EXIT, CH = 10000 (NOTE: maximum number is 65535)
; BH = 1000 BL = 100
; DH = 10 DL = 1 CL is destroyed on exit
;all other regs are preserved.
push AX ;save input value
push BP ;will use BP for temporarily storing 10000 figure
xor BX, BX ;initialize bin counters to zero
xor DX, DX
xor BP, BP
xor CH, CH ;probably unnecessary CH=0
Bin2Dec1:
cmp AX, CX ;remainder < denominator?
jb Bin2DecExit ;yes, done, exit
sub AX, CX ; subtract divisor (10d)
inc DH ; increment 10's counter
cmp DH, CL ;10's ctr overflow?
jb Bin2Dec1 ;no, keep going
inc BL ; increment 100's ctr
xor DH, DH ; & reset 10's
cmp BL, CL ; 100's ctr overflow?
jb Bin2Dec1
inc BH ;increment 1000's
xor BL, BL
cmp BH, CL ;1000's overflow?
jb Bin2Dec1
inc BP
xor BH, BH
cmp BP, CX ;10000's overflow?
jb Bin2Dec1
;should never reach this point
jmp FatalError ;this can't happen -- something is very wrong.
Bin2DecExit:
mov DL, AL ;value of 1's
mov CX, BP ;store value of 10000 in CH
mov CH, CL
pop BP ;restore regs
pop AX
ret
Bin2Dec endP
;+++++++++++++++++++++
EchoString proc near ;dumps asciiz string; DS:SI points to head
push CX ;save registers (note: AH is trashed)
push DX
mov AH, TTYout ;set up for Int 21h to write char in DL
mov CX,HexBufSize ;loop for finite # in case string not terminated with 0
EchoString1:
mov DL,[SI]
cmp DL,0
jz EchoStringExit ;terminate on binary 0
int 21h ;else display it
inc SI ;bump pointer
loop EchoString1 ;loop rather than jmp in case string not really AsciiZ
EchoStringExit:
call NewLine ;CRLF
pop DX ;restore registers
pop CX
ret
EchoString endP
;+++++++++++++++++++++
EchoAddr proc near ;echo [current address paragraph] pointed to by DI
;DI=one byte past last byte entered,
push AX ;save registers used
push BX
push CX
push DX
mov DL,CR ;return cursor to start of line
DOScall TTYout
mov CX, 4 ;4 bytes to give 16-bit word in Ascii
mov DX, DI ;point to string DX=end+1
sub DX,CX ;DX=start of string
mov BX, ConOut ;console out, no redirection
DOScall writeFile
pop DX ;restore original values
pop CX
pop BX
pop AX
ret
EchoAddr endP
;+++++++++++++++++++++
writeCR proc near ;send CR to console
push AX
push DX
mov DL, CR
DOScall TTYout
pop DX
pop AX
ret
writeCR endP
;+++++++++++++++++++++
NewLine proc near ;send CR LF to console, preserving regs
push AX
push DX
mov DL, CR
DOScall TTYout
mov DL, LF
DOScall TTYout
pop DX
pop AX
ret
NewLine endP
;+++++++++++++++++++++
subttl -- Data buffers, messages --
page
;--- messages ---
DOSerr db 'Wrong DOS version. Need DOS 2.0 or greater. BINTEL.COM ',Version
db CR,LF,'$'
HelpMsg db CR,LF
db 'Usage: BINTEL InputFile [/H]', CR,LF
db ' where InputFile = file to be processed.',CR,LF
db LF
db 'If no /h switch entered, the program converts a binary file to intel hex.',CR,LF
db 'If switch /h is entered, the program converts a hex file into binary.',CR,LF
db LF
db 'Default extensions:',CR,LF
db ' BIN,COM for binary files; HEX for Intel Hex files.',CR,LF
db LF
db 'Examples:', CR,LF
db '[assume TEST1.BIN and TEST1.COM both exist]',CR,LF
db LF
db ' bintel test1 converts TEST1.BIN to TEST1.HEX',CR,LF
db ' bintel test1.com converts TEST1.COM to TEST1.HEX',CR,LF
db ' bintel test2.exe converts TEST2.EXE to TEST2.HEX',CR,LF
db ' bintel hexfile /h converts HEXFILE.HEX to HEXFILE.BIN',CR,LF
db ' bintel \bin\foo.b converts \BIN\FOO.B to \BIN\FOO.HEX',CR,LF
db ' bintel bar.foo /h converts BAR.FOO to BAR.BIN',CR,LF
db LF
db 'BINTEL.COM is donated to the public domain.'
db CR, LF
HelpLen equ $-HelpMsg
BadSwitchMsg db 'Unrecognized command switch: /'
SwitchCode db 0, CR, LF
BadSwitchLen equ $-BadSwitchMsg
OpenErrMsg db CR, 'Error trying to open file: '
OpenErrLen equ $-OpenErrMsg
ReadErrMsg db CR, LF, 'Error reading file: '
ReadErrLen equ $-ReadErrMsg
WriteErrMsg db CR, LF, 'Error writing file: '
WriteErrLen equ $-WriteErrMsg
CloseErrMsg db CR, LF, 'Error trying to close file: '
CloseErrLen equ $-CloseErrMsg
ProcBinMsg db CR, 'Processing binary file: '
ProcBinLen equ $-ProcBinMsg
BinHead db CR, 'Address', CR, LF
db '-------', CR, LF
BinHeadLen equ $-BinHead
ProcHexMsg db CR, 'Processing [Intel Hex] file: '
ProcHexLen equ $-ProcHexMsg
HexHead db CR, 'Line # Para #', CR, LF
db '------ ------', CR, LF
HexHeadLen equ $-HexHead
OverflowMsg db ' *** WARNING: address counter overflowed. ', Bel, CR, LF
OverflowLen equ $-OverflowMsg
AddrErrMsg db ' *** WARNING: address counter disagrees with file: ', Bel
AddrErrLen equ $-AddrErrMsg
CRCerrMsg db ' *** WARNING: calculated CRC disagrees with file. ', CR, LF
CRCerrLen equ $-CRCerrMsg
PrematureMsg db CR, LF, ' *** End of file encountered before end of paragraph. *** '
db Bel,CR, LF
PrematureLen equ $-PrematureMsg
FatalErrMsg db CR,LF,' *** FATAL ERROR -- ABORTING -- BINTEL.COM *** ',Bel,CR,LF
FatalErrLen equ $-FatalErrMsg
;--- Data Buffers ---
even ;make buffer start on even address for DeskPro (8086)
BinBuffer db BinBufSize dup (?) ;NOTE: BinBuffer always even multiple of 16d
HexBuffer db HexBufSize dup (?)
;---------------------
Code endS
;================
end Main